Skip to content

feat(compile): torch.compile support for flagos as a first-class inductor GPU device - #41

Merged
zhaoyinglia merged 1 commit into
flagos-ai:mainfrom
lvyufeng:torch-compile
Aug 5, 2026
Merged

feat(compile): torch.compile support for flagos as a first-class inductor GPU device#41
zhaoyinglia merged 1 commit into
flagos-ai:mainfrom
lvyufeng:torch-compile

Conversation

@lvyufeng

@lvyufeng lvyufeng commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Summary

Enables torch.compile on the flagos device by registering flagos with
TorchInductor as a first-class GPU device. The traced graph is handed to
compile_fx unchanged, and inductor emits Triton kernels that operate on flagos
tensors directly — no conversion to cuda, no copy at the graph boundary.

import torch, torch_fl
x = torch.randn(4096, 4096, device="flagos:0")
compiled = torch.compile(my_model, backend="flagos")
y = compiled(x)

This works because flagos runs on the physical GPU that torch.cuda describes:
its allocator delegates to c10::cuda::CUDACachingAllocator, so a flagos
tensor's storage already is CUDA memory.

Why not the device-aliasing approach

The first revision of this PR rewrote the graph and its example inputs to cuda
before calling compile_fx. That is not just a copy per call — it breaks
backward.

at::getAccelerator() is PrivateUse1/flagos in this build, and
torch::autograd::Node::stream() only yields a stream when a node's input device
type equals the accelerator. A cuda-rewritten graph therefore produces
stream-less autograd nodes, and AOT autograd's backward trace inside compile_fx
trips opt_ready_stream && opt_parent_stream (engine.cpp:1085). That was the
cause of 8 of the 11 test failures.

Confirmed by differential test: eager backward on plain cuda fails identically,
with torch.compile never involved.

Registration surface

What Why
GPU_TYPES.append("flagos") is_gpu() is a membership test on that list object; without it inductor takes the C++/CPU codegen path and never emits Triton.
Prime get_gpu_type()'s cache It asserts at most one GPU type is available, and the torch.cuda shim reports available alongside flagos.
register_interface_for_device Device state from torch.flagos, hardware properties from torch.cuda (same physical GPU).
DeviceProperties.create wrap Reports flagos as cuda at the Triton boundary — Triton's NVIDIA backend hard-checks target.backend == "cuda", so a literal "flagos" finds 0 compatible backends. Inductor already does this in reverse for ROCm (hints.py:149).
register_device_op_overrides Device guard / stream / synchronize snippets. Must directly inherit CUDADeviceOpOverrides — attributes present on the base class never reach __getattr__ delegation.
register_backend_for_device The stock CUDA/Triton scheduling + wrapper codegen under the "flagos" key.

Two generated-kernel bugs that only surface under compilation

  • detach re-dispatched into itself. The generated kernel called
    at::detach(self), which is also registered on PrivateUse1, so it dispatched
    straight back — infinite recursion. Eager hid this because DeviceBoxingGuard
    rewrites self's device metadata first; under FakeTensor it cannot, since the
    Python dispatch key sits above the backend key. Dynamo traces every
    nn.Linear through detach, so this was a stack-overflow segfault at trace
    time. Now emits at::native::detach (NATIVE_DIRECT_VIEW_OPS).
  • gen_inplace didn't box optional<Tensor>. clamp_.Tensor handed unboxed
    flagos min/max to a CUDA self and crashed. Optionals are now materialized
    into holders, matching gen_functional_pure.

Both are covered by tests confirmed to fail (segfault at the exact asserting
line) against a build with the fixes reverted.

CPU-torch wheel accommodations

This build pairs a CPU-only pip torch with an external libtorch_cuda.so, so
several torch.cuda Python bindings are missing:

  • CudaInterface.get_raw_stream re-attached — the binding exists, but the
    import-time _is_compiled() probe left it None.
  • torch.cuda.memory_* routed to the flagos allocator backing the same pool.
  • flagos Event/Stream in place of the dummy base classes.
  • triton.cudagraphs = False (torch.cuda.CUDAGraph raises on construction) and
    use_static_cuda_launcher = False (not built).

flagos_compile_backend also accepts the mode/options/dynamic kwargs dynamo
forwards to named backends, expanding them into compile_fx config_patches
rather than mutating inductor's global config.

Testing

Rebased onto flagos/main (0700b61) and re-verified end to end on A100 in the
torch-fl-210 env:

Suite Result
tests/integration/test_compile.py 12 passed, 1 skipped (was 2 passed / 8 failed)
tests/integration/ops/test_clamp_dispatch.py 15 passed
tests/integration/ops/ (full sweep) 489 passed, 160 skipped, 1 xfailed, 3 xpassed
tests/integration/ops/test_rng_dispatch.py 104 passed, 2 skipped, 1 xfailed
test_ops / allocator / factory / fallback_trace / clone_dispatch 135 passed
tests/unit 15 passed

The rebase brought in the RNG generator-injection work (#39, #49), which touches
the same scripts/codegen_ops.py templates; the conflict was resolved keeping
both, and python scripts/codegen_ops.py was verified to reproduce the committed
cuda_kernels.cc byte-for-byte.

Open work

  • FlagTree integration to replace OpenAI Triton (scaffold present, gated by
    FLAGOS_USE_FLAGTREE=1, off by default)
  • Fusion-gain benchmarking against stock inductor+triton on cuda — the previous
    revision of this PR quoted speedup figures that were measured under the old
    device-aliasing design, so they no longer describe this code and have been
    dropped rather than restated.
  • Multi-GPU compilation not yet exercised

🤖 Generated with Claude Code

Wires torch.compile into the flagos (PrivateUse1) backend by registering
flagos with TorchInductor as a real GPU device, so the traced graph is
handed to compile_fx unchanged and inductor emits Triton kernels that
operate on flagos tensors directly.

The earlier approach rewrote the graph and its example inputs to cuda
before compiling. That is not just a copy per call: at::getAccelerator()
is PrivateUse1/flagos in this build, and torch::autograd::Node::stream()
only yields a stream when a node's input device type equals the
accelerator. A cuda-rewritten graph therefore produces stream-less
autograd nodes, and AOT autograd's backward trace inside compile_fx trips
opt_ready_stream && opt_parent_stream (engine.cpp:1085) -- the cause of
8 of the 11 test failures. Verified by differential test: eager backward
on plain cuda fails identically with torch.compile never involved.

Registration surface (device_interface.py, inductor_codegen.py):

* GPU_TYPES gains "flagos" in place -- is_gpu() is a membership test on
  that list object, and without it inductor takes the C++/CPU codegen
  path and never emits Triton. get_gpu_type()'s functools cache is primed
  while the list is narrowed, since it asserts at most one GPU type is
  available and the torch.cuda shim reports available too.
* DeviceInterface subclass: device state from torch.flagos, hardware
  properties from torch.cuda (same physical GPU, same allocator).
* DeviceProperties.create reports flagos as cuda at the Triton boundary.
  Triton's NVIDIA backend hard-checks target.backend == "cuda", so a
  literal "flagos" finds 0 compatible backends. Inductor already does
  this rewrite in the opposite direction for ROCm (hints.py:149).
* Device op overrides + scheduling/wrapper codegen: the stock CUDA/Triton
  pipeline under the "flagos" key, also published on torch.flagos for
  inductor's official PrivateUse1 hook.

Two generated-kernel bugs that only surface under compilation:

* detach re-dispatched into itself. The kernel called at::detach(self),
  also registered on PrivateUse1, so it dispatched straight back. Eager
  hid the recursion because DeviceBoxingGuard rewrites self's device
  metadata first; under FakeTensor it cannot, since the Python dispatch
  key sits above the backend key. Dynamo traces every nn.Linear through
  detach, so this was a stack-overflow segfault at trace time. Now emits
  at::native::detach (NATIVE_DIRECT_VIEW_OPS).
* gen_inplace passed only plain at::Tensor args to DeviceBoxingGuard, so
  clamp_.Tensor handed unboxed flagos min/max to a CUDA self and crashed.
  Optionals are now materialized into holders, matching gen_functional_pure.

Both regressions are covered by tests that were confirmed to fail (segfault
at the exact asserting line) against a build with the fixes reverted.

CPU-torch wheel accommodations, since torch.cuda's Python layer was frozen
without CUDA: re-attach CudaInterface.get_raw_stream (binding exists, the
import-time _is_compiled() probe left it None), route torch.cuda.memory_*
to the flagos allocator that backs the same pool, hand out flagos
Event/Stream in place of the dummy base classes, force triton.cudagraphs
off (torch.cuda.CUDAGraph raises on construction) and use_static_cuda_launcher
off (not built).

flagos_compile_backend now accepts the mode/options/dynamic kwargs dynamo
forwards to named backends and expands them into compile_fx config_patches,
rather than mutating inductor's global config.

Tests: test_compile.py 12 passed / 1 skipped (was 2 passed / 8 failed);
test_clamp_dispatch.py 15 passed; ops dispatch sweep 358 passed;
test_ops.py 58 passed; allocator/factory/fallback/unit 73 passed.

Docs updated to drop the device-aliasing description and the unmeasured
performance-parity figures; benchmarking remains open work.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@lvyufeng lvyufeng changed the title feat(compile): torch.compile support for flagos via inductor device aliasing feat(compile): torch.compile support for flagos as a first-class inductor GPU device Aug 4, 2026
@zhaoyinglia
zhaoyinglia merged commit f212295 into flagos-ai:main Aug 5, 2026
8 checks passed
lvyufeng pushed a commit to lvyufeng/PyTorch-Plugin-FL that referenced this pull request Aug 6, 2026
… flagos

The torch.compile integration merged in flagos-ai#41 only ever compiled single-Linear
models in its tests, which need neither autotuning nor more than one Triton
kernel. Two independent failures hid behind that. Both reproduce on any
graph with a couple of stacked Linears or a LayerNorm.

Autotuning needs a constructible event. InductorBenchmarker.get_event_pairs
times candidate configs with torch.cuda.Event(enable_timing=True). In the
CPU-only wheel this build pairs with an external libtorch_cuda.so, that
binding was never compiled, so torch.cuda substitutes a placeholder from
torch._utils._dummy_type whose __new__ raises "Tried to instantiate dummy
base class Event". flagos.Event subclassed it and inherited the failure.

flagos.Event now picks its base class by lineage: on a vendor torch build it
still subclasses torch.cuda.Event, and when that is a dummy it subclasses
the device-agnostic torch.Event, which dispatches record/block/query/
elapsedTime to c10::flagos::DeviceGuardImpl (csrc/runtime/guard.h). Timing
stays a real device measurement, and since every vendor under
csrc/runtime/accelerator/ implements that ABI, the fallback is portable
rather than NVIDIA-specific. Note the fix has to land here: patching
triton.testing.do_bench does not help, because inductor reaches the
benchmarker through triton_heuristics.benchmark_all_configs -> bench ->
benchmarker.benchmark_gpu, not through do_bench.

Compile workers need torch_fl. Inductor's default worker_start_method,
"subprocess", starts workers as a bare `sys.executable -m
torch._inductor.compile_worker` that imports only torch and triton. flagos
lives behind PrivateUse1, so such a worker has no accelerator: triton's
CudaDriver.is_active() asks torch.cuda.is_available(), gets False, and the
worker dies with "Could not find an active GPU backend". "fork" inherits
this process, torch_fl included, so workers come up already seeing the
device -- and compilation stays parallel, unlike compile_threads = 1
(Qwen3-0.6B: 31.9s forked vs 40.8s serial). Both overrides are scoped to
this build by probing for a missing torch._C CUDA binding, so a vendor
torch install keeps inductor's defaults.

tests/integration/test_compile_autotune.py guards both: stacked Linears,
normalizations, reductions, multi-kernel backward, dynamic shapes and
max-autotune, plus a direct check that the autotuner's own Event call works.
On a cleared TORCHINDUCTOR_CACHE_DIR, 7 of its 8 tests fail before these
changes.

CI ran no compile tests at all, so .github/configs/cuda.yml now runs both
compile files -- in one pytest invocation on purpose. The worker pool is
created lazily and shared, so a file run on its own can be served entirely
before the pool spins up, which is exactly how the worker failure stayed
hidden until a multi-file run reproduced it.

Measured on one A100, fp32, compiled vs eager on flagos: Qwen3-0.6B forward
2.24x (35.6ms -> 15.9ms, numerics matching eager at rtol/atol 2e-2),
elementwise chain 4096x4096 9.18x, transformer block 1.41x, matmul-bound
MLP 1.06x, and 0.92x at 64x512 where launch overhead exceeds the saving.

tests/perf/bench_compile.py had never been run and could not be: it called
torch.gelu (nonexistent), read torch.os.environ, recognised only the
"privateuseone" spelling of the device, and imported torch before torch_fl
-- which the docs now state as a hard requirement, since torch_fl preloads
the libtorch_cuda.so that torch.cuda depends on. The test suite gets away
without it because conftest imports torch_fl during collection.

Also documents a third bug found while benchmarking and left unfixed:
convolutions do not compile. Inductor prefers channels_last for conv on
GPU, and while the flagos conv kernel honours that layout, its fake/meta
kernel still predicts contiguous strides, so inductor rejects the graph on
a stride mismatch. Eager never hits it, since it is the layout pass that
produces a channels_last input. Reproduce with bench_compile.py --model=conv.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants